/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
/**
 * Software License, Version 1.0 Copyright 2003 The Trustees of Indiana
 * University. All rights reserved. Redistribution and use in source and binary
 * forms, with or without modification, are permitted provided that the
 * following conditions are met: 1) All redistributions of source code must
 * retain the above copyright notice, the list of authors in the original source
 * code, this list of conditions and the disclaimer listed in this license; 2)
 * All redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the disclaimer listed in this license in the
 * documentation and/or other materials provided with the distribution; 3) Any
 * documentation included with all redistributions must include the following
 * acknowledgement: "This product includes software developed by the Community
 * Grids Lab. For further information contact the Community Grids Lab at
 * http://communitygrids.iu.edu/." Alternatively, this acknowledgement may
 * appear in the software itself, and wherever such third-party acknowledgments
 * normally appear. 4) The name Indiana University or Community Grids Lab or
 * NaradaBrokering, shall not be used to endorse or promote products derived
 * from this software without prior written permission from Indiana University.
 * For written permission, please contact the Advanced Research and Technology
 * Institute ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202. 5)
 * Products derived from this software may not be called NaradaBrokering, nor
 * may Indiana University or Community Grids Lab or NaradaBrokering appear in
 * their name, without prior written permission of ARTI. Indiana University
 * provides no reassurances that the source code provided does not infringe the
 * patent or any other intellectual property rights of any other entity. Indiana
 * University disclaims any liability to any recipient for claims brought by any
 * other entity based on infringement of intellectual property rights or
 * otherwise. LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH
 * NO WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY
 * GIVES NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF
 * INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS.
 * INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS",
 * "VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.
 * LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR
 * ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION
 * GENERATED USING SOFTWARE.
 */
package cgl.narada.transport.sslHttpBase;

import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.Vector;

import org.apache.log4j.Logger;

import cgl.narada.transport.AccessDeniedException;
import cgl.narada.transport.Link;
import cgl.narada.transport.LinkFactory;
import cgl.narada.transport.TransportException;
import cgl.narada.transport.TransportHandler;

/**
 * A utility link factory implementation from which SSL/HTTP transport derive.
 * All the common code such as link management, link acceptor, ... are in this
 * class.
 * 
 * @author John Yin
 * @author Harshawardhan Gadgil
 * @version 1.0
 */

public abstract class LinkFactoryImpl implements LinkFactory,
		TransportDataListener {

	final static Logger log = Logger.getLogger(LinkFactoryImpl.class.getName());

	// A table of transport connections to different destinations.
	// "host:port" => Transport
	protected Hashtable m_key2transport = new Hashtable();
	protected Hashtable m_transport2key = new Hashtable();
	protected LinkManager m_linkManager = new LinkManager(null);
	protected TransportFactory m_factory = null;

	// Accepts incoming connections from another remote channle factory.
	protected TransportAcceptor m_transportAcceptor = null;

	protected long m_statusInterval = 0;
	protected TransportHandler m_transportHandler = null;
	private String m_type = null;

	/**
	 * -------------------------------------------------------------------
	 */
	public LinkFactoryImpl(TransportFactory factory, String type) {
		m_factory = factory;
		m_type = type;
	}

	/**
	 * -------------------------------------------------------------------
	 */
	public Enumeration getLinks() {
		return m_linkManager.getLinks();
	}

	/**
	 * -------------------------------------------------------------------
	 */
	public LinkManager getLinkManager() {
		return m_linkManager;
	}

	/**
	 * ------------------------------------------------------------------- Set
	 * the factory creation parameters. Pass through to set the transport
	 * factory's properties. listenerport - The port to listen on truststore -
	 * Location of the trusted authorities database keystore - Location of the
	 * public/private key database truststorePassword - Password to the
	 * truststore keystorePassword - Password to the keystore username - Proxy
	 * authenticated user name. password - Proxy authenticated password.
	 * https.proxyHost - The location of the HTTPS proxy. Will try to auto
	 * detect from System properties if this does not exist. https.proxyPort -
	 * The location of the HTTPS proxy port. Will try to auto detec from System
	 * properties if this does not exist. secure - true | false. If false, will
	 * not do any real SSL.
	 */
	public void setLinkFactoryProperties(Properties factoryProperties) {
		m_factory.setProperties(factoryProperties);
	}

	/**
	 * -------------------------------------------------------------------
	 * Create the physical SSL connection to the destination end point. Also
	 * start the listening SSL acceptor.
	 */
	public boolean loadLinkFactoryServices() throws TransportException {
		// Start SSL server.
		m_factory.initialize();

		// m_linkManager.start();

		m_transportAcceptor = new TransportAcceptor();
		m_transportAcceptor.start();

		return true;
	}

	/**
	 * -------------------------------------------------------------------
	 * 
	 * @param connectionProperties
	 *            Link properties.<br>
	 *            <ul>
	 *            <li>host - The transport's end point's host name or ip
	 *            address.
	 *            <li>port - The transport's end point's port number.
	 *            </ul>
	 *            <p>
	 * @exception TransportException
	 *                If unable to create a link due to IO errors.
	 * @exception AccessDeniedException
	 *                If during transport creation, some form of authentication
	 *                is required (user authentation for proxies)
	 */
	public Link createLink(Properties connectionProperties)
			throws TransportException {
		String hostStr = connectionProperties.getProperty("hostname");
		String portStr = connectionProperties.getProperty("portnum");

		if (hostStr == null || hostStr.length() <= 0) {
			throw new TransportException("No destination host specified");
		}
		try {
			Transport transport = ensureTransportExists(hostStr, portStr,
					connectionProperties);
			Link link = createLink(transport, connectionProperties);
			LinkMessageCommand cmd = new LinkMessageCommand(
					LinkMessageCommand.CREATE_CHANNEL, link.getLinkId());
			transport.sendData(cmd.toBytes());
			return link;
		}
		catch (IOException ioe) {
			throw new TransportException("IOException: " + ioe.getMessage());
		}
	}

	/**
	 * ------------------------------------------------------------------- By
	 * default the status interval is 1 second.
	 * 
	 * @param interval
	 *            The interval to check keep alive.
	 */
	public void setLinkStatusInterval(long interval) {
		m_statusInterval = interval;
		Enumeration e = m_transport2key.keys();
		while (e.hasMoreElements()) {
			Transport t = (Transport) e.nextElement();
			t.setStatusCheckInterval(m_statusInterval);
		}
	}

	/**
	 * -------------------------------------------------------------------
	 */
	public void setTransportHandler(TransportHandler transportHandler) {
		m_transportHandler = transportHandler;
		m_linkManager.setMeasurementService(m_transportHandler
				.getMeasurementService());
	}

	/**
	 * -------------------------------------------------------------------
	 */
	public void dispose() {
		m_transportAcceptor.destroy();
		m_linkManager.dispose();
	}

	/**
	 * ------------------------------------------------------------------- Makes
	 * sure that a transport connection to desginated host/port exists.
	 * 
	 * @param host
	 *            The destination endpoint's host
	 * @param port
	 *            The destination endpoint's port
	 */
	private Transport ensureTransportExists(String host, String port,
			Properties prop) throws TransportException {
		String key = host + ":" + port;
		Transport transport = (Transport) m_key2transport.get(key);
		if (transport == null) {
			transport = m_factory.createTransportInstance();
			transport.setStatusCheckInterval(m_statusInterval);
			transport.setProperties(prop);
			transport.connect();
			transport.setTransportDataListener(this);
			m_key2transport.put(key, transport);
			m_transport2key.put(transport, key);
		}
		return transport;
	}

	/**
	 * -------------------------------------------------------------------
	 * Handle performance message types
	 * 
	 * @return If the message was related to performance.
	 */
	public boolean handlePerfMessage(LinkMessage m) {
		if (m.getMessageType() != LinkMessagePerf.TRANSPORT_PERFORMANCE_TYPE) {
			return false;
		}
		LinkImpl c = (LinkImpl) m_linkManager.getLink(m.getLinkID());

		if (c == null) {
			log.warn("No link " + m.getLinkID() + " for performance message");
		}
		else {
			// Hande the message to the link.
			byte perfType = m.getPayload()[0];
			c.handlePerformanceMessage(m);
		}
		return true;
	}

	/**
	 * -------------------------------------------------------------------
	 * Handles incoming message from transports initiated from this factory.
	 */
	public void dataReceived(Transport transport, byte[] message) {
		if (m_transportHandler == null
				&& message[0] != LinkMessagePerf.TRANSPORT_PERFORMANCE_TYPE) {
			return;
		}
		LinkMessage m = new LinkMessage();

		try {
			m.parseData(message);
		}
		catch (IOException ioe) {
			log.error("Bad data received: " + ioe.getMessage());
		}
		if (handlePerfMessage(m)) return;

		String linkID = m.getLinkID();

		Object brokerID = null;
		LinkImpl c = (LinkImpl) m_linkManager.getLink(linkID);
		if (c == null) {
			log.warn("Received message on a non-existant link: " + linkID);
		}
		else {
			brokerID = c.getBrokerID();
		}

		if (brokerID != null) {
			m_transportHandler.dataReceived(m.getPayload(), brokerID);
		}
		else {
			m_transportHandler.dataReceived(m.getPayload(), m.getLinkID());
		}
	}

	/**
	 * -------------------------------------------------------------------
	 * Handles transport closed from transports initiated from this factory.
	 */
	public void transportClosed(Transport transport) {
		String key = (String) m_transport2key.get(transport);
		if (key == null) {
			log.warn("Transport closed but no key found");
		}
		else {
			m_key2transport.remove(key);
			m_transport2key.remove(transport);
		}

		manageTransportLoss(transport);
	}

	/**
	 * -------------------------------------------------------------------
	 * Handle the loss of a transport by notifying TransportHandler of link
	 * losses on this transport.
	 */
	private void manageTransportLoss(Transport transport) {
		// If we lost the transport by error, then notify transport handler
		// to manage the link loss.
		if (m_transportHandler != null && transport.isInErrorState()) {
			Enumeration e = m_linkManager.getLinks();
			while (e.hasMoreElements()) {
				LinkImpl c = (LinkImpl) e.nextElement();
				if (c.getTransport().equals(transport)) {
					m_transportHandler.manageLinkLoss(c);
				}
			}
		}

		// Now close all links.
		m_linkManager.closeAllLinks(transport);
	}

	/**
	 * -------------------------------------------------------------------
	 * Create a link handle with a specific link id and on a specific transport.
	 * Used mainly by accepting link creation requests from a remote broker
	 */
	private Link createLink(String linkID, Transport transport) {
		LinkImpl link = new LinkImpl(transport, m_type, m_linkManager);
		// Do not use setLinkId() because that generates a update link message.
		link.m_linkID = linkID;
		m_linkManager.addLink(link);

		if (m_transportHandler != null) {
			m_transportHandler.registerLink(this, link);
		}
		return link;
	}

	/**
	 * ------------------------------------------------------------------- Close
	 * a link.
	 */
	private void closeLink(String linkID) {
		Link c = m_linkManager.getLink(linkID);
		if (c == null) {
			log.warn("Closing non-existant link: " + linkID);
		}
		else {
			c.closeLink();
		}
	}

	/**
	 * -------------------------------------------------------------------
	 */
	private Link createLink(Transport transport, Properties creationProperties) {
		LinkImpl link = new LinkImpl(transport, m_type, m_linkManager);
		link.setCreationProperties(creationProperties);
		m_linkManager.addLink(link);

		if (m_transportHandler != null) {
			m_transportHandler.registerLink(this, link);
		}
		return link;
	}

	/**
	 * ==========================================================================
	 * Accepts incoming transport connection requests. After accepted, will add
	 * self as the transport data listener so when data comes in, it will
	 * deliver it to the appropriate Transport Handler.
	 */
	class TransportAcceptor extends Thread implements TransportDataListener {

		private boolean m_stopped = false;
		private Vector m_transports = new Vector();

		/**
		 * -------------------------------------------------------------------
		 */
		public void run() {
			setName("TransportAcceptor");
			while (!m_stopped) {
				try {
					// System.out.println("***>>>> Transport Acceptor
					// Waiting...");
					Transport transport = m_factory.accept();
					// System.out
					// .println("***>>>> Transport Acceptor Got connection");

					transport.setTransportDataListener(this);
					m_transports.addElement(transport);
				}
				catch (TransportException e) {}
			}
		}

		/**
		 * -------------------------------------------------------------------
		 */
		public void destroy() {
			if (!m_stopped) {
				m_stopped = true;
				interrupt();
				log.debug("<TransportAcceptor> Shutting down "
						+ m_transports.size() + " active accepted transports");
				Enumeration e = m_transports.elements();
				while (e.hasMoreElements()) {
					Transport transport = (Transport) e.nextElement();
					try {
						if (transport.isConnected()) {
							transport.disconnect();
						}
					}
					catch (IOException ioe) {}
				}
			}
		}

		/**
		 * -------------------------------------------------------------------
		 * Handles data received from transports initiated from another remote
		 * factory.
		 */
		public void dataReceived(Transport transport, byte[] message) {
			LinkMessage m = new LinkMessage();

			try {
				m.parseData(message);
			}
			catch (IOException ioe) {
				log.error("<TransportAcceptor> Bad data received: "
						+ ioe.getMessage());
				return;
			}

			switch (message[0]) {

			case LinkMessagePerf.TRANSPORT_PERFORMANCE_TYPE:
				handlePerfMessage(m);
				break;

			case LinkMessage.TRANSPORT_DATA_TYPE:
				if (m_transportHandler != null) {
					m_transportHandler.dataReceived(m.getPayload(), m
							.getLinkID());
				}
				break;
			case LinkMessageKeepAlive.TRANSPORT_KEEPALIVE_TYPE:
				break;

			case LinkMessageCommand.TRANSPORT_COMMAND_TYPE:
				LinkMessageCommand cmd = new LinkMessageCommand();
				try {
					cmd.parseData(message);
				}
				catch (IOException e) {
					log
							.error("<TransportAcceptor> Bad command message received: "
									+ e.getMessage());
					return;
				}
				if (cmd.getCommandType() == LinkMessageCommand.CREATE_CHANNEL) {
					createLink(cmd.getLinkID(), transport);
					log.info("<TransportAcceptor> Created inbound link "
							+ cmd.getLinkID() + " on transport " + transport);
				}
				else if (cmd.getCommandType() == LinkMessageCommand.DESTROY_CHANNEL) {
					log.info("<TransportAcceptor> Destroying inbound link "
							+ cmd.getLinkID() + " on transport " + transport);
					closeLink(cmd.getLinkID());
				}
				else if (cmd.getCommandType() == LinkMessageCommand.TRANSPORT_QUERY) {
					log.info("<TransportAcceptor> Transport query for: "
							+ cmd.getMessage());
					cmd.setMessage(transport.getType());
					try {
						transport.sendData(cmd.toBytes());
					}
					catch (IOException e) {}
					break;
				}
				else if (cmd.getCommandType() == LinkMessageCommand.UPDATE_CHANNEL_ID) {
					log.info("<TransportAcceptor> Updating link id");
					String oldID = cmd.getMessage();
					m_linkManager.renameLink(oldID, cmd.getLinkID());
				}
				else {
					log.error("<TransportAcceptor> Unrecognized command: "
							+ cmd.getCommandType());
				}
				break;
			}
		}

		/**
		 * -------------------------------------------------------------------
		 * Handles transport closed from transports initiated from another
		 * remote factory.
		 */
		public void transportClosed(Transport transport) {
			m_transports.removeElement(transport);
			manageTransportLoss(transport);
		}
	}
}
